/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package com.java.mc.utils;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Vector;

/**
 * A set of helper methods related to string manipulation.
 *
 */
public final class StringUtils {
	private static final long KILOBYTE = 1024;
	private static final long MEGABYTE = KILOBYTE * 1024;
	private static final long GIGABYTE = MEGABYTE * 1024;
	private static final long TERABYTE = GIGABYTE * 1024;
	private static final long PETABYTE = TERABYTE * 1024;

	/**
	 * constructor to stop anyone instantiating the class
	 */
	private StringUtils() {
	}

	/** the line separator for this OS */
	public static final String LINE_SEP = System.getProperty("line.separator");

	/**
	 * Splits up a string into a list of lines. It is equivalent to
	 * <tt>split(data, '\n')</tt>.
	 * 
	 * @param data
	 *            the string to split up into lines.
	 * @return the list of lines available in the string.
	 */
	public static Vector<String> lineSplit(String data) {
		return split(data, '\n');
	}

	/**
	 * Splits up a string where elements are separated by a specific character
	 * and return all elements.
	 * 
	 * @param data
	 *            the string to split up.
	 * @param ch
	 *            the separator character.
	 * @return the list of elements.
	 */
	public static Vector<String> split(String data, int ch) {
		Vector<String> elems = new Vector<String>();
		int pos = -1;
		int i = 0;
		while ((pos = data.indexOf(ch, i)) != -1) {
			String elem = data.substring(i, pos);
			elems.addElement(elem);
			i = pos + 1;
		}
		elems.addElement(data.substring(i));
		return elems;
	}

	/**
	 * Replace occurrences into a string.
	 * 
	 * @param data
	 *            the string to replace occurrences into
	 * @param from
	 *            the occurrence to replace.
	 * @param to
	 *            the occurrence to be used as a replacement.
	 * @return the new string with replaced occurrences.
	 */
	public static String replace(String data, String from, String to) {
		StringBuffer buf = new StringBuffer(data.length());
		int pos = -1;
		int i = 0;
		while ((pos = data.indexOf(from, i)) != -1) {
			buf.append(data.substring(i, pos)).append(to);
			i = pos + from.length();
		}
		buf.append(data.substring(i));
		return buf.toString();
	}

	/**
	 * Convenient method to retrieve the full stacktrace from a given exception.
	 * 
	 * @param t
	 *            the exception to get the stacktrace from.
	 * @return the stacktrace from the given exception.
	 */
	public static String getStackTrace(Throwable t) {
		StringWriter sw = new StringWriter();
		PrintWriter pw = new PrintWriter(sw, true);
		t.printStackTrace(pw);
		pw.flush();
		pw.close();
		return sw.toString();
	}

	/**
	 * Checks that a string buffer ends up with a given string. It may sound
	 * trivial with the existing JDK API but the various implementation among
	 * JDKs can make those methods extremely resource intensive and perform
	 * poorly due to massive memory allocation and copying. See
	 * 
	 * @param buffer
	 *            the buffer to perform the check on
	 * @param suffix
	 *            the suffix
	 * @return <code>true</code> if the character sequence represented by the
	 *         argument is a suffix of the character sequence represented by the
	 *         StringBuffer object; <code>false</code> otherwise. Note that the
	 *         result will be <code>true</code> if the argument is the empty
	 *         string.
	 */
	public static boolean endsWith(StringBuffer buffer, String suffix) {
		if (suffix.length() > buffer.length()) {
			return false;
		}
		// this loop is done on purpose to avoid memory allocation performance
		// problems on various JDKs
		// StringBuffer.lastIndexOf() was introduced in jdk 1.4 and
		// implementation is ok though does allocation/copying
		// StringBuffer.toString().endsWith() does massive memory
		// allocation/copying on JDK 1.5
		// See http://issues.apache.org/bugzilla/show_bug.cgi?id=37169
		int endIndex = suffix.length() - 1;
		int bufferIndex = buffer.length() - 1;
		while (endIndex >= 0) {
			if (buffer.charAt(bufferIndex) != suffix.charAt(endIndex)) {
				return false;
			}
			bufferIndex--;
			endIndex--;
		}
		return true;
	}

	/**
	 * xml does not do "c" like interpretation of strings. i.e. \n\r\t etc. this
	 * method processes \n, \r, \t, \f, \\ also subs \s -> " \n\r\t\f" a
	 * trailing '\' will be ignored
	 *
	 * @param input
	 *            raw string with possible embedded '\'s
	 * @return converted string
	 * @since Ant 1.7
	 */
	public static String resolveBackSlash(String input) {
		StringBuffer b = new StringBuffer();
		boolean backSlashSeen = false;
		for (int i = 0; i < input.length(); ++i) {
			char c = input.charAt(i);
			if (!backSlashSeen) {
				if (c == '\\') {
					backSlashSeen = true;
				} else {
					b.append(c);
				}
			} else {
				switch (c) {
				case '\\':
					b.append((char) '\\');
					break;
				case 'n':
					b.append((char) '\n');
					break;
				case 'r':
					b.append((char) '\r');
					break;
				case 't':
					b.append((char) '\t');
					break;
				case 'f':
					b.append((char) '\f');
					break;
				case 's':
					b.append(" \t\n\r\f");
					break;
				default:
					b.append(c);
				}
				backSlashSeen = false;
			}
		}
		return b.toString();
	}

	/**
	 * Takes a human readable size representation eg 10K a long value. Doesn't
	 * support 1.1K or other rational values.
	 * 
	 * @param humanSize
	 *            the amount as a human readable string.
	 * @return a long value representation
	 * @throws Exception
	 *             if there is a problem.
	 * @since Ant 1.7
	 */
	public static long parseHumanSizes(String humanSize) throws Exception {
		long factor = 1L;
		char s = humanSize.charAt(0);
		switch (s) {
		case '+':
			humanSize = humanSize.substring(1);
			break;
		case '-':
			factor = -1L;
			humanSize = humanSize.substring(1);
			break;
		default:
			break;
		}
		// last character isn't a digit
		char c = humanSize.charAt(humanSize.length() - 1);
		if (!Character.isDigit(c)) {
			int trim = 1;
			switch (c) {
			case 'K':
				factor *= KILOBYTE;
				break;
			case 'M':
				factor *= MEGABYTE;
				break;
			case 'G':
				factor *= GIGABYTE;
				break;
			case 'T':
				factor *= TERABYTE;
				break;
			case 'P':
				factor *= PETABYTE;
				break;
			default:
				trim = 0;
			}
			humanSize = humanSize.substring(0, humanSize.length() - trim);
		}
		return factor * Long.parseLong(humanSize);
	}

	/**
	 * Removes the suffix from a given string, if the string contains that
	 * suffix.
	 * 
	 * @param string
	 *            String for check
	 * @param suffix
	 *            Suffix to remove
	 * @return the <i>string</i> with the <i>suffix</i>
	 */
	public static String removeSuffix(String string, String suffix) {
		if (string.endsWith(suffix)) {
			return string.substring(0, string.length() - suffix.length());
		} else {
			return string;
		}
	}

	/**
	 * Removes the prefix from a given string, if the string contains that
	 * prefix.
	 * 
	 * @param string
	 *            String for check
	 * @param prefix
	 *            Prefix to remove
	 * @return the <i>string</i> with the <i>prefix</i>
	 */
	public static String removePrefix(String string, String prefix) {
		if (string.startsWith(prefix)) {
			return string.substring(prefix.length());
		} else {
			return string;
		}
	}
}
